﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Xml;
//
using Jc.MicroDesigner.Management;

namespace Jc.MicroDesigner.Objects
{
    /// <summary>
    /// Элемент типа "провод"
    /// </summary>
    public sealed class Wire : CircuitElement
    {
        /// <summary>
        /// Особый класс-отрисовщик для провода
        /// </summary>
        public sealed class WireImage : Drawings.ElementImage
        {
            private List<Point> _Points = new List<Point>();
            //Сторона квадаратика на пине
            const int PinQuad = 4;

            /// <summary>
            /// Точки - узлы перегиба провода
            /// </summary>
            public List<Point> Points
            {
                get
                {
                    return _Points;
                }
            }

            /// <summary>
            /// Создать отображение провода
            /// </summary>
            /// <param name="parent">Провод</param>
            public WireImage(Wire parent)
                : base(parent)
            {
                this.onPaint += new Jc.MicroDesigner.Events.onPaintElement(WireImage_onPaint);
            }

            /// <summary>
            /// Отрисовать PIN
            /// </summary>
            /// <param name="g">Graphics</param>
            /// <param name="p">Point</param>
            private void DrawPin(Graphics g, Point p)
            {
                Brush brush = new SolidBrush(Color.Black);
                g.FillRectangle(brush, p.X - PinQuad / 2, p.Y - PinQuad / 2, PinQuad, PinQuad);
            }

            /// <summary>
            /// Отрисовка провода
            /// </summary>
            /// <param name="img">Изображение</param>
            /// <param name="element">Элемент</param>
            private void WireImage_onPaint(Image img, BaseElement element)
            {
                Graphics g = Graphics.FromImage(img);
                Pen wire_pen = new Pen(Color.Black, 2.0f);
                //отрисовка провода по звеньям
                for (int i = 0; i < _Points.Count - 1; i++)
                {
                    g.DrawLine(wire_pen, _Points[i], _Points[i + 1]);
                }
                if ((_Points.Count > 0) && (_Parent != null))
                {
                    Wire wire = _Parent as Wire;
                    DrawPin(g, _Points[0]);
                    DrawPin(g, _Points[_Points.Count - 1]);
                }
            }

            /// <summary>
            /// Проверка принадлежности точки порводу
            /// </summary>
            /// <param name="x">X</param>
            /// <param name="y">Y</param>
            /// <returns>Результат</returns>
            public override bool CheckSelection(int x, int y)
            {
                bool result = false;

                int i = 0;
                int left = 0, top = 0;
                int width = 0, height = 0;
                //проверим, попадает ли точка в базовый прямоугольник фигуры
                result = base.CheckSelection(x, y);
                if (result)
                {
                    result = false;
                    while ((i < _Points.Count - 1) && (!result))
                    {
                        if (_Points[i].X == _Points[i + 1].X)
                        {
                            height = Math.Abs(_Points[i].Y - _Points[i + 1].Y) + 6;
                            width = 4;
                            left = _Points[i].X + Left - 3;
                            top = Math.Min(_Points[i].Y, _Points[i + 1].Y) - 3 + Top;
                        }
                        else
                            if (_Points[i].Y == _Points[i + 1].Y)
                            {
                                height = 4;
                                width = Math.Abs(_Points[i].X - _Points[i + 1].X) + 6;
                                left = Math.Min(_Points[i].X, _Points[i + 1].X) - 3 + Left;
                                top = _Points[i].Y + Top - 3;
                            }
                        result = (x >= left) && (x <= left + width) && (y >= top) && (y <= top + height);
                        i++;
                    }
                }

                return result;
            }

            /// <summary>
            /// Проверяет, на каком звене лежит точка, и сообщает первую точку звена
            /// </summary>
            /// <param name="x">X координата</param>
            /// <param name="y">Y</param>
            /// <returns>Номер первой точки звена</returns>
            public int NearestWirePoint(int x, int y)
            {
                int result = -1;

                int i = 0;
                int left = 0, top = 0;
                int width = 0, height = 0;
                //проверим, попадает ли точка в базовый прямоугольник фигуры
                if (base.CheckSelection(x, y))
                {
                    bool res = false;
                    while ((i < _Points.Count - 1) && (!res))
                    {
                        if (_Points[i].X == _Points[i + 1].X)
                        {
                            height = Math.Abs(_Points[i].Y - _Points[i + 1].Y) + 6;
                            width = 4;
                            left = _Points[i].X + Left - 3;
                            top = Math.Min(_Points[i].Y, _Points[i + 1].Y) - 3 + Top;
                        }
                        else
                            if (_Points[i].Y == _Points[i + 1].Y)
                            {
                                height = 4;
                                width = Math.Abs(_Points[i].X - _Points[i + 1].X) + 6;
                                left = Math.Min(_Points[i].X, _Points[i + 1].X) - 3 + Left;
                                top = _Points[i].Y + Top - 3;
                            }
                        res = (x >= left) && (x <= left + width) && (y >= top) && (y <= top + height);
                        i++;
                    }
                    if (i < _Points.Count)
                        result = i - 1;
                }

                return result;
            }

            /// <summary>
            /// Добавить массив точек 
            /// </summary>
            /// <param name="list">Список точек</param>
            public void AddPoints(List<Point> list)
            {
                for (int i = 0; i < list.Count; i++)
                    AddPoint(list[i].X, list[i].Y);
            }

            /// <summary>
            /// Добавить точку с абсолютными координатами
            /// </summary>
            /// <param name="x">X</param>
            /// <param name="y">Y</param>
            public void AddPoint(int x, int y)
            {
                Point p = new Point(x - Left, y - Top);
                #region определение ширины и высоты
                int max_x = 0, max_y = 0;
                if (p.X + 4 > Width)
                    max_x = p.X - Width + 4;
                if (p.Y + 4 > Height)
                    max_y = p.Y - Height + 4;

                int min_y = 0, min_x = 0;
                if (p.Y - 4 < 0)
                {
                    min_y = p.Y - 4;
                    Top = Top + min_y;
                }
                if (p.X - 4 < 0)
                {
                    min_x = p.X - 4;
                    Left = Left + min_x;
                }

                int dx = 0;
                int dy = 0;

                if (max_x > 0)
                {
                    Width = Width + max_x + 4;
                }
                if (min_x < 0)
                {
                    Width = Width - min_x + 4;
                    dx = 0 - min_x;
                }

                if (max_y > 0)
                {
                    Height = Height + max_y + 4;
                }
                if (min_y < 0)
                {
                    Height = Height - min_y + 4;
                    dy += 0 - min_y;
                }
                #endregion
                if ((min_y < 0) || (min_x < 0))
                {
                    p.X = p.X + dx;
                    p.Y = p.Y + dy;

                    Point[] new_surface = new Point[_Points.Count];
                    for (int i = 0; i < _Points.Count; i++)
                    {
                        new_surface[i] = new Point(_Points[i].X + dx, _Points[i].Y + dy);
                    }
                    _Points.Clear();
                    _Points.AddRange(new_surface);
                    //не забываем сдвинуть пины
                    if (_Parent != null)
                    {
                        Wire wire = _Parent as Wire;
                        wire.PINs[0].DrawOptions.Left += dx;
                        wire.PINs[0].DrawOptions.Top += dy;
                        (wire.PINs[0].DrawOptions as PIN.PINImage).BindingPoint =
                            new Point(wire.PINs[0].DrawOptions.Left + 2, wire.PINs[0].DrawOptions.Top + 2);

                        wire.PINs[1].DrawOptions.Left += dx;
                        wire.PINs[1].DrawOptions.Top += dy;
                        (wire.PINs[1].DrawOptions as PIN.PINImage).BindingPoint =
                            new Point(wire.PINs[1].DrawOptions.Left + 2, wire.PINs[1].DrawOptions.Top + 2);
                    }
                }

                ((_Parent as Wire).PINs[1].DrawOptions as PIN.PINImage).BindingPoint = new Point(x, y);

                _Points.Add(p);
                //обновляем свой холст
                _TemplateImage = null;
            }

            /// <summary>
            /// Пины проводов не хранят статически своих точек соединений и свойств рисования
            /// Поэтому их свойства рисования устанавливаются по требованию
            /// </summary>
            /// <param name="x">X</param>
            /// <param name="y">Y</param>
            /// <returns>Пин для соединения</returns>
            public override PIN GetBindingPIN(int x, int y)
            {
                PIN res = null;
                Point p = _Points[0];
                if (_Points.Count > 0)
                {
                    if ((Math.Abs(p.X - x + Left) < PinQuad) && (Math.Abs(p.Y - y + Top) < PinQuad))
                    {
                        res = (_Parent as Wire).PINs[1];
                    }
                    else
                    {
                        p = _Points[_Points.Count - 1];
                        if ((Math.Abs(p.X - x + Left) < PinQuad) && (Math.Abs(p.Y - y + Top) < PinQuad))
                        {
                            res = (_Parent as Wire).PINs[0];
                        }
                    }
                }
                else
                    res = (_Parent as Wire).PINs[0];
                if (res != null)
                    (res.DrawOptions as PIN.PINImage).BindingPoint = p;
                return res;
            }
        }

        private bool _Blocked = false;

        /// <summary>
        /// Создать провод
        /// </summary>
        /// <param name="name">Имя объекта</param>
        public Wire(string name)
            : base(name, 2)
        {
            _PINCollection[0].onRecieve += new Jc.MicroDesigner.Events.onRecieveHandler(onRecieveSignal);
            _PINCollection[1].onRecieve += new Jc.MicroDesigner.Events.onRecieveHandler(onRecieveSignal);

            _DrawOptions = new WireImage(this);
        }

        private void onRecieveSignal(PIN sender)
        {
            if (!_Blocked)
            {
                if (sender == _PINCollection[0])
                    _PINCollection[1].Send(sender.Signal);
                else
                    _PINCollection[0].Send(sender.Signal);
            }
        }

        /// <summary>
        /// Загрузить точки провода
        /// </summary>
        /// <param name="nodes">Секции конфига</param>
        public override void LoadAdditionals(Dictionary<ConfigSection, XmlNode> nodes)
        {
            XmlNode additionals_node = nodes[ConfigSection.Additionals];
            XmlNode points = null;
            int i = 0;
            while ((i < additionals_node.ChildNodes.Count) && (additionals_node.ChildNodes[i].Name != "points"))
                i++;
            if (i < additionals_node.ChildNodes.Count)
            {
                points = additionals_node.ChildNodes[i];
                foreach (XmlNode p in points.ChildNodes)
                {
                    int x = Convert.ToInt32(p.Attributes["x"].Value);
                    int y = Convert.ToInt32(p.Attributes["y"].Value);
                    WireImage img = DrawOptions as WireImage;
                    img.Points.Add(new Point(x, y));
                }
                DrawOptions.RefreshTemplate();
            }
        }

        /// <summary>
        /// Сохранить точки провода
        /// </summary>
        /// <param name="nodes">Ветви свойств</param>
        public override void SaveAdditionals(Dictionary<ConfigSection, XmlNode> nodes)
        {
            XmlNode additionals_node = nodes[ConfigSection.Additionals];
            XmlDocument doc = additionals_node.OwnerDocument;
            XmlNode points = doc.CreateElement("points");
            WireImage img = DrawOptions as WireImage;
            foreach (Point p in img.Points)
            {
                XmlNode point = doc.CreateElement("point");
                SchemeSerializer.CreateXmlAttr(point, "x", p.X.ToString());
                SchemeSerializer.CreateXmlAttr(point, "y", p.Y.ToString());
                points.AppendChild(point);
            }
            additionals_node.AppendChild(points);
        }

        /// <summary>
        /// Включена ли передача сигналов
        /// </summary>
        public bool Blocked
        {
            get
            {
                return _Blocked;
            }
            set
            {
                _Blocked = value;
            }
        }

        /// <summary>
        /// Проставить все события для пинов
        /// </summary>
        public void UpdateEvents()
        {
            _PINCollection[0].onRecieve += new Jc.MicroDesigner.Events.onRecieveHandler(onRecieveSignal);
            _PINCollection[1].onRecieve += new Jc.MicroDesigner.Events.onRecieveHandler(onRecieveSignal);
        }
    }
}